<?php
// FIXME: should be abstract?
/**
 * Represents object model, e.g. News, User.
 * Allows to: 
 *  - manage object - seting and getting it's data,
 *  - save - store object using proper mapper.
 * 
 * @author Damian Sromek <damianpaq@gmail.com>
 * @version 0.1
 */
// TODO: lazy initialization of every field?
// TODO: comment
abstract class Plus_Model_Model_Abstract
{	
	/**
	 * Data mapper for this model.
	 * @var Plus_Model_Mapper_Abstract
	 */
	protected $_mapper;
	
	/**
	 * Required data mapper class name.
	 * @var Plus_Model_Mapper_Abstract or it's child class
	 */
	//protected $_mapperClassName;
	
	/**
	 * Logger.
	 * @var Zend_Log
	 */
	protected $_log;
	
	/**
	 * array(
	 * 		'variableName' => 'Variable_Method_Getter/Setter_Postfix',
	 *		'name' => 'Name', 
	 *		'id' => 'UserId' 
	 * )
	 * @var array array with object fields that should be returned when using
	 * getDataArray() method
	 */
	protected $_descriptionVariables;
	
	/**
	 * Constructor.
	 * Creates object's model with (or without) specified options and mapper.
	 * @param $options array options representing model's data
	 * @param $mapperClassName mapper class allowed for this model
	 * @return void
	 * @throws Exception if mapper class is not specified
	 */
	public function __construct(array $options = array(), $mapper)
	{
		$this->_log = Zend_Registry::get('log');
		$this->_log->info(get_class($this) . ' - start');
		
		if (null === $mapper) {
			$this->_log->err(get_class($this) . ' - bad mapper!');
			throw new Exception('Mapper class not specified');
		}
		//$this->_mapperClassName = $mapperClassName;
		$this->_mapper = $mapper; 
		//Plus_Model_Mapper_Abstract::factory($mapperClassName);
		$this->setOptions($options);
		$this->_log->info(get_class($this) . ' - OK');
	}
	
	/**
	 * Checks if setter is allowed/available and sets specified variable value.
	 * @param $name name of setter method postfix, e.g. Name
	 * @param $value parameter value for the setter
	 * @return void
	 * @throws Exception if invalid or forbidden setter is called
	 */
	// TODO: what's that for?
	/*public function __set($name, $value)
	{
		$method = 'set' . $name;
		var_dump(get_class_methods($this));
		if (('mapper' == $name) || !method_exists($this, $method)) {
			throw new Exception('Invalid setter: ' . $method . "($value)");
		}
		$this->_log->info(get_class($this) . ' -> ' . $method . "($value)");
		$this->$method($value);
	}*/
	
	/**
	 * Checks if getters is allowed/available and returns specified method value.
	 * @param $name nam of getter method postfix, e.g. Name
	 * @return mixed value returned by specified getter
	 * @throws Exception if invalid or forbidden gettes is called
	 */
	// TODO: what's that for?
	/*public function __get($name)
	{
		$method = 'get' . $name;
		$this->_log->info(get_class($this) . ' - looking for - ' . $method . "()");
		if (('mapper' == $name) || !method_exists($this, $method)) {
			throw new Exception('Invalid property');
		}
		$this->_log->info(get_class($this) . ' -> ' . $method . "($value)");
		return $this->$method();
	}*/
	
	/**
	 * Sets model options from specified array.
	 * @param $options array with objects data values
	 * @return $this
	 */
	public function setOptions(array $options)
	{
		$methods = get_class_methods($this);
		$method;
		foreach ($options as $key => $value) {
			$method = 'set' . $key;
			$this->_log->info('Checking for method ' . $method);
			if (in_array($method, $methods)) {
				$this->_log->info('Running method ' . $method . "($value)");
				$this->$method($value);
			} else {
				$this->_log->info($method . ' not found');
			}
		}
		return $this;
	}
	
	/**
	 * Sets mapper used by this model.
	 * @param $mapper mapper we want to use by this model
	 * @return $this
	 */
	// TODO: should this be public? or be at all?
	/*public function setMapper($mapper) {
		$this->_mapper = $mapper;
		return $this;
	}*/
	
	/**
	 * 
	 * @return Plus_Model_Mapper_Abstract mapper used for this model
	 */
	public function getMapper()
	{
		/*if (null === $this->_mapper) {
			$this->_log->info(get_class($this) . ' - creating mapper');
			$this->_mapper = $this->_mapperClassName::factory();
		}*/
		return $this->_mapper;
	}
	
	/**
	 * Adds information about this model protected variable used to store data
	 * and getter/setter method postfix which allows to get/set that variable 
	 * from the outside.
	 * If $methodPostfix parameter is = null then as the postfix we use the $variableName.
	 * @param $variableName name of the protected variable 
	 * @param $methodPostfix getter/setter method postfix
	 * @return $this
	 */
	protected function addVar($variableName, $methodPostfix = null)
	{
		$this->_log->info(get_class($this) . " - adding '$variableName' description");
		$method = ($methodPostfix === null) ? $variableName : $methodPostfix;
		$this->_descriptionVariables[$variableName] = $method;
		return $this;
	}
	
	/**
	 * Saves model using it's mapper.
	 * @return void
	 * @throws Exception if error occured while saving object
	 */
	public function save()
	{
		if ($this->getMapper() !== null) {
			$this->getMapper()->save($this);
		}
	}
	
	/**
	 * Deletes model using it's mapper.
	 * @return void
	 * @throws Exception if error occured during delete object
	 */
	public function delete()
	{
		if ($this->getMapper() !== null) {
			$this->getMapper()->delete($this);
		}
	}
	
	/**
	 * Returns array with data inside the object.
	 * @return array association array with data inside the object
	 */
	public function getDataArray()
	{
		$data = array();
		$tmpMethod = '';
		foreach ($this->_descriptionVariables as $variableName => $method) {
			$tmpMethod = 'get' . $method;
			$data[$variableName] = $tmpMethod();
		}
		return $data;
	}
}